﻿module Sources


open System
open System.IO
open System.Runtime.Serialization


[<DataContract>]
type SourceLocation(line, column, index) = 

   [<DataMember>]
   member val Line : int = line with get, set

   [<DataMember>]
   member val Column : int = column with get, set

   [<DataMember>]
   member val Index : int = index with get, set
   

type SourceReader(stream : Stream) =    
   let _reader = new StreamReader(stream)
   
   let mutable _line = 1
   let mutable _column = 1
   let mutable _index = 1

   member public this.Peek() =
      _reader.Peek()

   member public this.Read() =      
      let peek = (char)(this.Peek())
      this.Move()
      peek

   member public this.Move() =
      let value = (char)(_reader.Read())

      _index <- _index + 1

      if (value = '\n') then
         _line <- _line + 1
         _column <- 1
      else
         _column <- _column + 1   

   member public this.SetLocation(location : SourceLocation) =
      _reader.BaseStream.Position <- (int64)(location.Index - 1)
      _reader.DiscardBufferedData()
           
      _index <- location.Index
      _line <- location.Line
      _column <- location.Column
      
   member public this.GetLocation() =
      SourceLocation(_line, _column, _index)
      
   member public this.IsEnded() =
      _reader.EndOfStream

   interface IDisposable with
      member this.Dispose() =
         _reader.Dispose()


let IsBoxed(source : String option) =
   if (source.IsSome) then
      (source.Value.Length > 0) && (source.Value.[0] = '"' || source.Value.[0] = '`')   
   else
      false


let SkipComments(reader : SourceReader) =
   let rec recurse(inComment : int) =
      let peek = reader.Peek()
   
      if (peek <> -1) then
         let next = (char)(peek)

         if (next = '{') then
            reader.Move()
            recurse(inComment + 1)

         elif (inComment > 0 && next = '}') then
            reader.Move()
            recurse(inComment - 1)

         elif (inComment > 0) then
            reader.Move()
            recurse(inComment)

   recurse(0)   


let SkipIrrelevant(reader : SourceReader) =      
   let rec recurse() =
      let peek = reader.Peek()
   
      if (peek <> -1) then
         let next = (char)(peek)

         if (Char.IsWhiteSpace(next)) then
            reader.Move()            
            recurse()

         elif (next = '{') then
            SkipComments(reader)
            recurse()

   recurse()   


let ReadWord(reader : SourceReader) =
   let rec recurse(value : string) =   
      let peek = reader.Peek()

      if (peek = -1) then
         value
      else
         let next = (char)(peek)
         
         // TODO: simplify?
         if (Char.IsWhiteSpace(next) 
         || next = '.' 
         || next = '{'          
         || next = '"' 
         || next = '`' 
         || next = '<' 
         || next = '[' 
         || next = '{' 
         || next = '(' 
         || next = ''') then
            value
         else
            reader.Move()
            recurse(value + next.ToString())

   let word = recurse("")

   if (word = "") then
      None
   else
      Some(word)


let ReadSequence(reader : SourceReader, starting : char, ending : char) =   
   let rec recurse(value : string, inSeq : bool) =      
      let peek = reader.Peek()

      if (peek = -1) then
         value
      else
         let next = (char)(peek)
      
         if (not inSeq && next <> starting) then
            ""
         else
            reader.Move()

            if (inSeq && next = ending) then         
               value + next.ToString()
            elif (not inSeq && next = ending) then
               recurse(value + next.ToString(), true)
            else         
               recurse(value + next.ToString(), inSeq)

   let sequence = recurse("", false)

   if (sequence = "") then
      None
   else
      Some(sequence)


let ReadSource(reader : SourceReader) =   
   let peek = reader.Peek()

   if (peek = -1) then
      None
   else 
      let next = (char)(peek)

      // TODO: tests for more of these
      if (next = '"') then
         ReadSequence(reader, '"', '"')
      elif (next = '`') then
         ReadSequence(reader, '`', '`')
      elif (next = ''') then
         ReadSequence(reader, ''', ''')
      elif (next = '[') then
         ReadSequence(reader, '[', ']')
      elif (next = '<') then
         ReadSequence(reader, '<', '>')
      elif (next = '(') then
         ReadSequence(reader, '(', ')')
      else
         ReadWord(reader) 


